﻿using System;
using System.Linq;

namespace TLang.Parsers
{
    using Ast;

    public class Lexer
    {

        public readonly String file;
        public readonly String text;

        // current offset indicators
        private int offset;
        private int line;
        private int col;


        public Lexer(string text, String file)
        {
            if (string.IsNullOrEmpty(text) && string.IsNullOrEmpty(file))
            {
                throw new ArgumentNullException("content和file必须提供一个");
            }
            if (string.IsNullOrEmpty(file))
            {
                this.file = string.Empty;
            }
            else
            {
                this.file = file;
            }
            if (string.IsNullOrEmpty(text))
            {
                this.text = Util.ReadFile(file);
            }
            else
            {
                this.text = text;
            }
            this.offset = 0;
            this.line = 0;
            this.col = 0;

            if (this.text == null)
            {
                Util.Abort("failed to read file: " + file);
            }

            Delimeter.AddDelimiterPair(Constants.PAREN_BEGIN, Constants.PAREN_END);
            Delimeter.AddDelimiterPair(Constants.CURLY_BEGIN, Constants.CURLY_END);
            Delimeter.AddDelimiterPair(Constants.SQUARE_BEGIN, Constants.SQUARE_END);
        }


        private void Forward()
        {
            if (text[offset] == '\n')
            {
                line++;
                col = 0;
                offset++;
            }
            else
            {
                col++;
                offset++;
            }
        }


        private void Skip(int n)
        {
            for (int i = 0; i < n; i++)
            {
                Forward();
            }
        }


        private bool SkipSpaces()
        {
            bool found = false;

            while (offset < text.Length &&
                    Char.IsWhiteSpace(text[offset]))
            {
                found = true;
                Forward();
            }
            return found;
        }


        public bool SkipComments()
        {
            bool found = false;

            if (text.StartsWith(Constants.LINE_COMMENT, offset))
            {
                found = true;

                // skip to line end
                while (offset < text.Length && text[offset] != '\n')
                {
                    Forward();
                }
                if (offset < text.Length)
                {
                    Forward();
                }
            }
            return found;
        }


        public void SkipSpacesAndComments()
        {
            while (SkipSpaces() || SkipComments())
            {
                // actions are performed by skipSpaces() and skipComments()
            }
        }


        public Node ScanString()
        {
            int start = offset;
            int startLine = line;
            int startCol = col;
            Skip(Constants.STRING_START.Length);    // skip quote mark

            while (true)
            {
                // detect runaway strings at end of file or at newline
                if (offset >= text.Length || text[offset] == '\n')
                {
                    throw new ParserException("runaway string", startLine, startCol, offset);
                }

                // end of string
                else if (text.StartsWith(Constants.STRING_END, offset))
                {
                    Skip(Constants.STRING_END.Length);    // skip quote mark
                    break;
                }

                // skip any char after STRING_ESCAPE
                else if (text.StartsWith(Constants.STRING_ESCAPE, offset) && offset + 1 < text.Length)
                {
                    Skip(Constants.STRING_ESCAPE.Length + 1);
                }

                // other characters (string content)
                else
                {
                    Forward();
                }
            }

            int end = offset;
            String content = text.Substring(
                    start + Constants.STRING_START.Length,
                    end - Constants.STRING_END.Length - start - Constants.STRING_START.Length);

            return new Str(content, file, start, end, startLine, startCol);
        }


        public static bool IsNumberChar(char c)
        {
            return Char.IsLetter(c) || Char.IsDigit(c) || c == '.' || c == '+' || c == '-';
        }


        public Node ScanNumber()
        {
            int start = offset;
            int startLine = line;
            int startCol = col;

            while (offset < text.Length && IsNumberChar(text[offset]))
            {
                Forward();
            }

            String content = text.Substring(start, offset - start);

            IntNum intNum = IntNum.parse(content, file, start, offset, startLine, startCol);
            if (intNum != null)
            {
                return intNum;
            }
            else
            {
                FloatNum floatNum = FloatNum.parse(content, file, start, offset, startLine, startCol);
                if (floatNum != null)
                {
                    return floatNum;
                }
                else
                {
                    throw new ParserException("incorrect number format: " + content, startLine, startCol, start);
                }
            }
        }


        public static bool IsIdentifierChar(char c)
        {
            return Char.IsLetter(c) || Char.IsDigit(c) || Constants.IDENT_CHARS.Contains(c);
        }


        public Node ScanNameOrKeyword()
        {
            int start = offset;
            int startLine = line;
            int startCol = col;

            while (offset < text.Length && IsIdentifierChar(text[offset]))
            {
                Forward();
            }

            String content = text.Substring(start, offset - start);
            if (content.StartsWith(":"))
            {
                return new Keyword(content.Substring(1), file, start, offset, startLine, startCol);
            }
            else
            {
                return new Name(content, file, start, offset, startLine, startCol);
            }
        }

        public Node NextToken()
        {

            SkipSpacesAndComments();

            // end of file
            if (offset >= text.Length)
            {
                return null;
            }

            // case 1. delimiters
            char cur = text[offset];
            if (Delimeter.IsDelimiter(cur))
            {
                Node ret = new Delimeter(cur.ToString(), file, offset, offset + 1, line, col);
                Forward();
                return ret;
            }

            // case 2. string
            if (text.StartsWith(Constants.STRING_START, offset))
            {
                return ScanString();
            }

            // case 3. number
            if (Char.IsDigit(text[offset]) ||
                    ((text[offset] == '+' || text[offset] == '-')
                            && offset + 1 < text.Length && Char.IsDigit(text[offset + 1])))
            {
                return ScanNumber();
            }

            // case 4. name or keyword
            if (IsIdentifierChar(text[offset]))
            {
                return ScanNameOrKeyword();
            }

            // case 5. syntax error
            throw new ParserException("unrecognized syntax: " + text.Substring(offset, 1),
                    line, col, offset);
        }
    }
}
